/*
 * Copyright (C) 2015 Miquel Sas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package traderlib.core.data.db.sql;

import java.util.List;
import traderlib.core.data.db.Column;
import traderlib.core.data.db.Filter;
import traderlib.core.data.db.Record;
import traderlib.core.data.db.Table;
import traderlib.core.data.db.View;
import traderlib.core.data.types.KeyPointer;
import traderlib.core.data.types.Value;

/**
 * A builder of INSERT statements. Insert can be of the following forms:
 * <ol>
 * <li>INSERT INTO table SELECT view</li>
 * <li>INSERT INTO table (columns...) VALUES (values...)</li>
 * <li>INSERT INTO table a record.</li>
 * </ol>
 *
 * @author Miquel Sas
 */
public class Insert extends Statement {

	/**
	 * The table to insert the record or the view into.
	 */
	private Table table;
	/**
	 * The columns to insert.
	 */
	private List<Column> columns;
	/**
	 * The list of values to insert.
	 */
	private List<Value> values;
	/**
	 * The select to insert.
	 */
	private Select select;
	/**
	 * A reference to the record when inserting one.
	 */
	private Record record;

	/**
	 * Default constructor.
	 */
	public Insert() {
		super();
	}

	/**
	 * Returns the table to insert into.
	 *
	 * @return The table.
	 */
	public Table getTable() {
		return table;
	}

	/**
	 * Sets the table to insert into.
	 *
	 * @param table The table to insert into.
	 */
	public void setTable(Table table) {
		this.table = table;
	}

	/**
	 * Returns the list of columns to inserted.
	 *
	 * @return The list of columns.
	 */
	public List<Column> getColumns() {
		return columns;
	}

	/**
	 * Sets the list of columns to be updated. If not set, the columns of the table are used instead.
	 *
	 * @param columns The list of columns to be updated.
	 */
	public void setColumns(List<Column> columns) {
		this.columns = columns;
	}

	/**
	 * Returns the list of values to insert.
	 *
	 * @return The list of values.
	 */
	@Override
	public List<Value> getValues() {
		return values;
	}

	/**
	 * Adds a list of values to build an insert statement of the form INSERT INTO table ( columns ) VALUES ( values ).
	 * This method can be called several times to create several insert operations. Obviously the values must correspond
	 * to the columns.
	 *
	 * @param values The list of values.
	 */
	public void setValues(List<Value> values) {
		this.values = values;
	}

	/**
	 * Returns the record to insert.
	 *
	 * @return The reocrd to insert.
	 */
	public Record getRecord() {
		return record;
	}

	/**
	 * Adds the list of values to build an insert statement of the form INSERT INTO table ( columns ) VALUES ( values )
	 * using the values given in the argument record. If the columns are not set and record has columns, the columns are
	 * taken from the record. Obviously the values in the record must correspond to the columns if already set.
	 *
	 * @param record The record to use to get the values.
	 */
	public void setRecord(Record record) {
		this.record = record;
	}

	/**
	 * Sets the select query to build an insert statement of the form INSERT INTO table ( columns ) SELECT ( query ).
	 *
	 * @param select The select builder.
	 */
	public void setSelect(Select select) {
		if (!getDatabaseAdapter().equals(select.getDatabaseAdapter())) {
			throw new IllegalArgumentException("The Select and the Insert databaseadapters must bethe same");
		}
		this.select = select;
	}

	/**
	 * Sets the select query to build an insert statement of the form INSERT INTO table ( columns ) SELECT ( query ).
	 *
	 * @param view The select view.
	 * @param filter The view filter.
	 */
	public void setSelect(View view, Filter filter) {
		@SuppressWarnings("LocalVariableHidesMemberVariable")
		Select select = new Select();
		select.setDatabaseAdapter(getDatabaseAdapter());
		select.setView(view);
		select.setWhere(filter);
		this.select = select;
	}

	/**
	 * Returns this <code>INSERT</code> query as a string, eventually with parameters.
	 *
	 * @return The statement.
	 */
	@Override
	public String toSQL() {

		if (table == null) {
			throw new UnsupportedOperationException("Malformed insert statement: need table");
		}
		if (record == null && values == null && select == null) {
			throw new UnsupportedOperationException("Malformed insert statement: need record, values or select");
		}
		if (values != null && select != null) {
			throw new UnsupportedOperationException("Malformed insert statement: set only values or select");
		}
		if (record != null && select != null) {
			throw new UnsupportedOperationException("Malformed insert statement: set only record or select");
		}
		if (values != null && record != null) {
			throw new UnsupportedOperationException("Malformed insert statement: set only values or record");
		}
		if (select != null && columns == null) {
			throw new UnsupportedOperationException("Malformed insert statement: columns are needed for a select");
		}
		if (select != null && select.getView().getColumnCount() != getColumns().size()) {
			throw new UnsupportedOperationException("Malformed insert statement: invalid number of columns");
		}
		if (getDatabaseAdapter() == null) {
			throw new UnsupportedOperationException("The database adapter must be set");
		}

		if (record != null) {
			values = record.getPersistentValues();
		}

		StringBuilder b = new StringBuilder(256);
		b.append("INSERT INTO ");
		b.append(table.getNameSchema());

		b.append(" (");
		if (columns != null) {
			boolean comma = false;
			List<Column> columnsInsert = getColumns();
			for (Column column : columnsInsert) {
				if (comma) {
					b.append(", ");
				}
				b.append(column.getNameCreate());
				comma = true;
			}
		}
		if (record != null) {
			boolean comma = false;
			List<KeyPointer> persistentKeyPointers = record.getPersistentKeyPointers();
			for (KeyPointer pointer : persistentKeyPointers) {
				Column column = record.getColumn(pointer.getIndex());
				if (comma) {
					b.append(", ");
				}
				b.append(column.getNameCreate());
				comma = true;
			}
		}
		b.append(") ");

		if (select != null) {
			b.append(select.toSQL());
			return b.toString();
		}

		if (values != null) {
			b.append("VALUES (");
			if (columns != null) {
				boolean comma = false;
				List<Column> columnsInsert = getColumns();
				for (Column column : columnsInsert) {
					if (comma) {
						b.append(", ");
					}
					if (column.isCurrentDate()) {
						b.append(getDatabaseAdapter().getCurrentDate());
					} else if (column.isCurrentTime()) {
						b.append(getDatabaseAdapter().getCurrentTime());
					} else if (column.isCurrentTimestamp()) {
						b.append(getDatabaseAdapter().getCurrentTimestamp());
					} else {
						b.append("?");
					}
					comma = true;
				}
			}
			if (record != null) {
				boolean comma = false;
				List<KeyPointer> persistentKeyPointers = record.getPersistentKeyPointers();
				for (KeyPointer pointer : persistentKeyPointers) {
					Column column = record.getColumn(pointer.getIndex());
					if (comma) {
						b.append(", ");
					}
					if (column.isCurrentDate()) {
						b.append(getDatabaseAdapter().getCurrentDate());
					} else if (column.isCurrentTime()) {
						b.append(getDatabaseAdapter().getCurrentTime());
					} else if (column.isCurrentTimestamp()) {
						b.append(getDatabaseAdapter().getCurrentTimestamp());
					} else {
						b.append("?");
					}
					comma = true;
				}
			}
			b.append(")");
			return b.toString();
		}

		return b.toString();
	}
}
